package com.zytech.zspider.task.impl.http;

import com.google.common.base.Function;
import com.google.common.collect.Maps;
import com.zytech.modules.lang.Langs;
import com.zytech.modules.lang.Requires;
import com.zytech.zspider.core.TaskDefinition;
import com.zytech.zspider.core.TaskResult;
import com.zytech.zspider.logger.Loggers;
import com.zytech.zspider.task.TaskContext;
import com.zytech.zspider.task.impl.ForkJoinTaskExecutor;

import java.util.Map;

/**
 * @author siuming
 */
public abstract class AbstractHttpTaskExecutor extends ForkJoinTaskExecutor {

    private final HttpTaskRequestExecutor requestExecutor;

    /**
     * @param scriptEvaluator
     */
    protected AbstractHttpTaskExecutor(String[] externalScripts,
                                       HttpTaskRequestExecutor requestExecutor) {
        this(DEFAULT_BLOCKING_FACTOR, externalScripts, requestExecutor);
    }

    /**
     * @param blockingFactor
     * @param evaluator
     * @param requestExecutor
     */
    protected AbstractHttpTaskExecutor(float blockingFactor,
                                       String[] externalScripts,
                                       HttpTaskRequestExecutor requestExecutor) {
        super(blockingFactor, externalScripts);
        Requires.notNull(requestExecutor, "requestExecutor must not be null.");
        this.requestExecutor = requestExecutor;
    }

    @Override
    public final boolean isTaskSupports(TaskDefinition task) {
        Requires.notNull(task, "task must not be null.");
        return isHttpTaskSupports(task, task.getSettings());
    }

    /**
     * @param task
     * @param settings
     * @return
     */
    protected boolean isHttpTaskSupports(TaskDefinition task,
                                         Map<String, String> settings) {
        return true;
    }

    @Override
    protected final TaskResult onTaskSubmit(TaskDefinition task, TaskContext context) {

        try {

            Loggers.me().info(getClass(), "submit {}.", task);
            Langs.Stopwatch stopwatch = Langs.newStopwatch();
            long startTime = stopwatch.start();

            HttpTaskRequest request = ofTaskRequest(task);
            HttpTaskResponse response = getTaskRequestExecutor().submit(request, task.getSettings());

            TaskContext collectContext = ofCollectTaskResultContext(task, context, request, response);
            Map<String, Object> collectResults = collectTaskResult(task.getCollectExprs(), collectContext);

            stopwatch.stop();
            long elapsedTime = stopwatch.elapsed();
            return TaskResult
                    .ofBuilder()
                    .taskname(task.getName())
                    .values(collectResults)
                    .startTime(startTime)
                    .elapsedTime(elapsedTime)
                    .build();
        } finally {
            releaseCollectTaskResultContext(task, context);
        }
    }

    /**
     * @param task
     * @return
     */
    protected final HttpTaskRequest ofTaskRequest(TaskDefinition task) {
        HttpTaskRequest.Verb verb = HttpTaskRequest.Verb.valueOf(task.getMethod().toUpperCase());
        return HttpTaskRequest.of(task.getUrl(), verb).params(task.getParams()).body(task.getBody()).headers(task.getHeaders());
    }

    private Map<String, Object> collectTaskResult(final Map<String, String> scripts,
                                                  final TaskContext context) {
        return Maps.transformValues(scripts, new Function<String, Object>() {
                    @Override
                    public Object apply(String script) {
                        return getExpressionEvaluator().eval(script, context);
                    }
                }
        );
    }

    /**
     * @param task
     * @param context
     * @param request
     * @param response
     * @return
     */
    protected abstract TaskContext ofCollectTaskResultContext(TaskDefinition task,
                                                              TaskContext context,
                                                              HttpTaskRequest request,
                                                              HttpTaskResponse response);

    /**
     * @param task
     * @param context
     */
    protected void releaseCollectTaskResultContext(TaskDefinition task, TaskContext context) {
    }

    @Override
    protected void doShutdown() {
        getTaskRequestExecutor().shutdown();
    }

    /**
     * @return
     */
    protected final HttpTaskRequestExecutor getTaskRequestExecutor() {
        return requestExecutor;
    }
}
